#pragma once

#include "Common.hpp"
#include "PageCache.hpp"

/*声明*/

// 此中心缓存应该只存在一个。这里使用单例模式 - 饿汉模式
class CentralCache
{
public:
	static CentralCache* GetInstance()
	{
		return &_sInt;
	}

	size_t FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size);
	span* GetOneSpan(SpanList& list, size_t size);
	void ReleaseListToSpans(void* start, size_t size);
private:
	SpanList _spanList[NFREELIST];
private:
	CentralCache()
	{}
	CentralCache(CentralCache&) = delete;
	CentralCache& operator=(CentralCache&) = delete;

	static CentralCache _sInt;
};

/*定义*/

CentralCache CentralCache::_sInt;  // 定义对象

// 至少返回一个完整的span，如果不存在需要向上层page获取
span* CentralCache::GetOneSpan(SpanList& list, size_t size)
{
	// 先遍历此对应的双链表，查看里面是否还存在可与给出的span
	span* it = list.Begin();
	while (it != list.End())
	{
		if (it->_freeList) return it;
		it = it->_next;
	}
	// 解除桶锁，为释放缓解申请压力，并且不怕此时申请同样的桶，他们会等page锁
	list._mtx.unlock();
	// 退出循环说明不存在未使用完的span了，此时我们需要向上层申请一个span，利用一个规则根据我们传入的size返回应该申请多少页的大小
	PageCache::GetInstance()->_pageMtx.lock();  // 此时需要上锁
	span* cur = PageCache::GetInstance()->NewSpan(SizeClass::NumMovePage(size));  // 成功申请到了
	cur->_isUse = true;  // 此时获得一个新的span了，将其设置为使用状态防止pagecache回收解决外碎片问题时回收了被使用的span
	cur->_objSize = size;
	PageCache::GetInstance()->_pageMtx.unlock();
	// 此时对此大块内存进行切分对应大小的小块，存放在自由链表中
	// 已知span的页号，我们首先计算出起始地址 以及span的大小
	char* start = (char*)(cur->_pageld << PAGE_SHIFT);
	size_t bytes = cur->_n << PAGE_SHIFT;
	char* end = start + bytes;

	cur->_freeList = start;
	start += size;
	void* res = cur->_freeList;
	int i = 1;
	while (start < end)
	{
		i++;
		NextObj(res) = start;
		start += size;
		res = NextObj(res);
	}
	NextObj(res) = nullptr;  // 千万注意这里需要置空
	// 此时切割完毕，头插入双向循环链表中去
	list._mtx.lock();
	list.HeadInsert(cur);
	return cur;
}

// 返回一个期望个数对应类型的内存链表
size_t CentralCache::FetchRangeObj(void*& start, void*& end, size_t batchNum, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanList[index]._mtx.lock();  // 哈希桶锁 只有是这个对应的线程之间才进行互斥，否则就是并行

	span* block = GetOneSpan(_spanList[index], size);  // 能够保证获取一个span对象，否则抛出异常
	assert(block && block->_freeList);

	start = block->_freeList;
	end = start;
	size_t actualNum = 1;
	while (actualNum < batchNum && NextObj(end) != nullptr)
	{
		end = NextObj(end);
		++actualNum;
	}
	block->_freeList = NextObj(end);
	NextObj(end) = nullptr;  // 截断
	block->_useCount += actualNum;  // 分出去的个数

	_spanList[index]._mtx.unlock();

	return actualNum;
}

// 用于回收threadcache回收的自由链表，挂好span
void CentralCache::ReleaseListToSpans(void* start, size_t size)
{
	size_t index = SizeClass::Index(size);
	_spanList[index]._mtx.lock();  // 桶锁

	void* next = nullptr;
	while (start)
	{
		next = NextObj(start);
		span* tmp = PageCache::GetInstance()->MapObjectToSpan(start);
		tmp->_useCount--;
		NextObj(start) = tmp->_freeList;  // 头插
		tmp->_freeList = start;

		if (tmp->_useCount == 0)
		{
			// 说明全部还回来了
			_spanList->Erase(tmp);  // 先剔除
			// 对此span进行一个初始化工作即可
			tmp->_freeList = nullptr;
			tmp->_prev = tmp->_next = nullptr;

			_spanList[index]._mtx.unlock();

			PageCache::GetInstance()->_pageMtx.lock();
			PageCache::GetInstance()->ReleaseSpanToPageCache(tmp);
			PageCache::GetInstance()->_pageMtx.unlock();

			_spanList[index]._mtx.lock();
		}

		start = next;
	}
	_spanList[index]._mtx.unlock();
}